package com.tim.apps.mockgps.service;

import java.util.Vector;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.location.LocationManager;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

import com.tim.apps.mockgps.model.GPSPosition;

public class MockLocationService extends Service {
	private static final String LOG_TAG = "MockLocationProvider";
	
	private Vector<GPSPosition> gpsPositions =
			new Vector<GPSPosition>(30);
	
	private LocationManager mLocationManager;
	
	private Thread simThread = null;
	
	public void addLocation(double lat,	double lng, double altitude,
			float bearing, float speed, float accuracy)
	{
		Log.d(LOG_TAG, "adding location: lat=" + lat + " lng=" + lng +
				" altitude=" + altitude + " bearing=" + bearing +
				" speed=" + speed + " accuracy=" + accuracy);
		
		GPSPosition gpsPos = new GPSPosition(lat, lng, altitude, bearing, speed, accuracy);
		gpsPositions.add(gpsPos);
	}
	
	public void clearLocations()
	{
		Log.d(LOG_TAG, "clearing locations");

		stopSimulationThread();
		gpsPositions.clear();
	}
	
	public void startSimulation()
	{
		Log.d(LOG_TAG, "starting simulation");
		stopSimulationThread();
		
		mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
		
		simThread = new Thread(new SimulationThread());
		simThread.start();
	}
	
	public void stopSimulation() {
		stopSimulationThread();
		MockLocationService.this.stopSelf();

		Log.d(LOG_TAG, "simulation stopped");
	}

	private void stopSimulationThread() {
		if (simThread == null) {
			Log.d(LOG_TAG, "stopping simulation thread: not running");
			return;
		}
		Log.d(LOG_TAG, "stopping simulation thread");
		
		simThread.interrupt();
		final int waitingTimeMs = 5000;
		try {
			if (simThread != null) {
				simThread.join(waitingTimeMs);
			}
		}
		catch (InterruptedException e) {
			Log.e(LOG_TAG, "cannot join with SimulationThread" +
					" after " + waitingTimeMs + "ms");
		}
	}
	
	public class SimulationThread implements Runnable {
		public void run() {
			Log.d(LOG_TAG, "SimulationThread started with " +
					gpsPositions.size() + " waypoints");
			
			try {
				int currPos = 0;
				while (!Thread.interrupted()) {				
					Log.d(LOG_TAG, "Interpolating between position " + currPos + "/" + (currPos+1));
					GPSPosition fromPos = gpsPositions.get(currPos);
					GPSPosition toPos = gpsPositions.get(Math.min(currPos+1, gpsPositions.size()-1)); 
					Log.d(LOG_TAG, "fromPos=" + fromPos);
					Log.d(LOG_TAG, "toPos=" + toPos);
					interpolateGPSPosition(fromPos, toPos);
					if (currPos < gpsPositions.size()-1) {
						++currPos;
					}
				}
				
			} catch (Exception e) {
				Log.e(LOG_TAG, "Exception in SimulationThread: " + e.getMessage());
			}
			
			Log.d(LOG_TAG, "SimulationThread terminates");

		}
		
		public void interpolateGPSPosition(GPSPosition fromPos, GPSPosition toPos) {
			double speedKmH = fromPos.getSpeedMPerSec() * 3.6;
			final long gpsSampleIntervalMs = 100;
			final double milliSecondsPerHour = 1000.0 * 60 * 60;

			double distance = fromPos.distanceKmTo(toPos);
			double distancePerStepKm =
					speedKmH * gpsSampleIntervalMs / milliSecondsPerHour;
			int maxSteps = (int)(distance / distancePerStepKm) + 1;
			double bearing = GPSPosition.bearing(fromPos, toPos);
			toPos.setBearing(bearing);
			Log.d(LOG_TAG, "distance=" + distance +
					", maxSteps=" + maxSteps + ", bearing=" + bearing);
			
			
			for (int step = 0; step < maxSteps; ++step) {
				try {
					// TODO adjust length of sleep based on elapsed real time
					Thread.sleep(gpsSampleIntervalMs);

				} catch (InterruptedException e) {
					Log.d(LOG_TAG, "SimulationThread interrupted during sleep");
					Thread.currentThread().interrupt();
					break;
				}

				// Set one position
				Location location = new Location(LocationManager.GPS_PROVIDER);
				
				GPSPosition currGpsPos =
						GPSPosition.interpolate(fromPos, toPos,
								(float)step / maxSteps);
				
				currGpsPos.writeTo(location);

				Log.v(LOG_TAG, location.toString());

				// set the time in the location. If the time on this
				// location
				// matches the time on the one in the previous set call, it
				// will be
				// ignored
				location.setTime(System.currentTimeMillis());

				mLocationManager.setTestProviderLocation(
						LocationManager.GPS_PROVIDER, location);
			}
			
		}

	}

	@Override
	public IBinder onBind(Intent intent) {
		return binder;
	}
	
	public class MyBinder extends Binder {
		public MockLocationService getService() {
			return MockLocationService.this;
		}
	}
	
	private final IBinder binder = new MyBinder();
}
